use std::collections::{HashMap, HashSet};

use pest::error::{Error as PestError, LineColLocation};
use pest::iterators::{Pair, Pairs};
use pest::Parser;

use crate::lexer::{Rule, SysyParser};
use crate::typecheck::{ArrayType, FunctionType, Type};

/// Run semantic analysis on the given source and return all discovered
/// semantic errors. When no errors are found `Ok(())` is returned.
pub fn analyze(source: &str) -> Result<(), Vec<SemanticError>> {
    let mut pairs = match SysyParser::parse(Rule::CompUnit, source) {
        Ok(pairs) => pairs,
        Err(err) => return Err(vec![SemanticError::from_parse_error(err)]),
    };

    let Some(comp_unit) = pairs.next() else {
        return Ok(());
    };

    let mut analyzer = SemanticAnalyzer::default();
    analyzer.visit_comp_unit(comp_unit);

    if analyzer.errors.is_empty() {
        Ok(())
    } else {
        Err(analyzer.errors)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SemanticError {
    pub code: u8,
    pub line: usize,
    pub message: String,
}

impl SemanticError {
    fn new(code: u8, line: usize, message: impl Into<String>) -> Self {
        Self { code, line, message: message.into() }
    }

    fn from_parse_error(err: PestError<Rule>) -> Self {
        let line = match err.line_col {
            LineColLocation::Pos((line, _)) | LineColLocation::Span((line, _), _) => line,
        };
        SemanticError::new(0, line, err.variant.message())
    }
}

#[derive(Debug, Clone)]
enum SymbolKind {
    Variable { ty: Type, _is_const: bool },
    Function(FunctionType),
}

#[derive(Default)]
struct Scope {
    symbols: HashMap<String, SymbolKind>,
}

#[derive(Default)]
struct SemanticAnalyzer {
    scopes: Vec<Scope>,
    errors: Vec<SemanticError>,
    current_return: Option<Type>,
}

impl SemanticAnalyzer {
    fn visit_comp_unit(&mut self, pair: Pair<Rule>) {
        self.enter_scope(); // global scope
        for child in pair.into_inner() {
            match child.as_rule() {
                Rule::ConstDecl => self.visit_const_decl(child),
                Rule::VarDecl => self.visit_var_decl(child),
                Rule::FuncDef => self.visit_func_def(child),
                Rule::TOP_INVALID => self.push_error(line_of(&child), ErrorKind::Invalid("invalid top-level declaration")),
                _ => {}
            }
        }
        self.exit_scope();
    }

    fn visit_const_decl(&mut self, pair: Pair<Rule>) {
        let mut inner = pair.into_inner();
        // Skip 'const' and base type (currently only int)
        inner.next();
        inner.next();

        for child in inner {
            match child.as_rule() {
                Rule::ConstDef => self.visit_const_or_var_def(child, true),
                Rule::CONSTDEF_INVALID => self.push_error(line_of(&child), ErrorKind::Invalid("invalid constant declaration")),
                Rule::COMMA => {}
                _ => {}
            }
        }
    }

    fn visit_var_decl(&mut self, pair: Pair<Rule>) {
        let mut inner = pair.into_inner();
        // Skip base type
        inner.next();

        for child in inner {
            match child.as_rule() {
                Rule::VarDef => self.visit_const_or_var_def(child, false),
                Rule::VARDEF_INVALID => self.push_error(line_of(&child), ErrorKind::Invalid("invalid variable declaration")),
                Rule::COMMA => {}
                _ => {}
            }
        }
    }

    fn visit_const_or_var_def(&mut self, pair: Pair<Rule>, is_const: bool) {
        let mut inner = pair.clone().into_inner();
        let Some(ident) = inner.next() else { return; };
        let name = ident.as_str();
        let ident_line = line_of(&ident);

        let mut dims = 0usize;
        let mut initializer: Option<Pair<Rule>> = None;
        for token in inner {
            match token.as_rule() {
                Rule::L_BRACKT => dims += 1,
                Rule::ASSIGN => {
                    initializer = None; // next token will be init value
                }
                Rule::InitVal | Rule::ConstInitVal => {
                    initializer = Some(token);
                    break;
                }
                _ => {}
            }
        }

        let mut ty = Type::Int;
        if dims > 0 {
            ty = make_array_type(Type::Int, dims);
        }

        if !self.declare(name, ident_line, SymbolKind::Variable { ty: ty.clone(), _is_const: is_const }) {
            return;
        }

        if let Some(init) = initializer {
            self.check_initializer(&ty, init, ident_line);
        }
    }

    fn visit_func_def(&mut self, pair: Pair<Rule>) {
        let mut inner = pair.clone().into_inner();

        let Some(ret_token) = inner.next() else { return; };
        let return_type = match ret_token.as_str() {
            "int" => Type::Int,
            "void" => Type::Void,
            _ => Type::Int,
        };

        let Some(ident) = inner.next() else { return; };
        let func_name = ident.as_str();
        let func_line = line_of(&ident);

        // skip '('
        inner.next();

        let mut params: Vec<Parameter> = Vec::new();
        let mut block_pair: Option<Pair<Rule>> = None;

        while let Some(token) = inner.next() {
            match token.as_rule() {
                Rule::FuncFParams => {
                    params = self.collect_params(token);
                }
                Rule::R_PAREN => {}
                Rule::Block => {
                    block_pair = Some(token);
                    break;
                }
                _ => {}
            }
        }

        let function_type = FunctionType {
            ret_ty: Box::new(return_type.clone()),
            params_type: params.iter().map(|p| p.ty.clone()).collect(),
        };

        if !self.declare(func_name, func_line, SymbolKind::Function(function_type.clone())) {
            return; // recovery strategy: skip entire function body
        }

        let Some(body) = block_pair else { return; };

        self.enter_scope();
        for param in &params {
            self.declare(&param.name, param.line, SymbolKind::Variable { ty: param.ty.clone(), _is_const: false });
        }

        let saved_return = self.current_return.clone();
        self.current_return = Some(return_type);
        self.visit_block_pairs(body.into_inner());
        self.current_return = saved_return;
        self.exit_scope();
    }

    fn collect_params(&mut self, pair: Pair<Rule>) -> Vec<Parameter> {
        let mut params = Vec::new();
        let mut seen = HashSet::new();
        for item in pair.into_inner() {
            if item.as_rule() != Rule::FuncFParam {
                continue;
            }
            let mut inner = item.into_inner();
            // Skip base type (only int at the moment)
            inner.next();
            let Some(ident) = inner.next() else { continue; };
            let name = ident.as_str().to_string();
            let line = line_of(&ident);
            let dims = count_dimensions(inner);
            let ty = make_array_type(Type::Int, dims);

            if !seen.insert(name.clone()) {
                self.push_error(line, ErrorKind::RedefinedVariable { name: name.clone() });
                continue;
            }

            params.push(Parameter { name, line, ty });
        }
        params
    }

    fn visit_block(&mut self, pair: Pair<Rule>) {
        self.enter_scope();
        self.visit_block_pairs(pair.into_inner());
        self.exit_scope();
    }

    fn visit_block_pairs(&mut self, pairs: Pairs<Rule>) {
        for child in pairs {
            match child.as_rule() {
                Rule::ConstDecl => self.visit_const_decl(child),
                Rule::VarDecl => self.visit_var_decl(child),
                Rule::Stmt => self.visit_stmt(child),
                Rule::INVALID => self.push_error(line_of(&child), ErrorKind::Invalid("invalid statement")),
                _ => {}
            }
        }
    }

    fn visit_stmt(&mut self, pair: Pair<Rule>) {
        let children: Vec<_> = pair.clone().into_inner().collect();
        if children.is_empty() {
            return;
        }
        match children[0].as_rule() {
            Rule::LVal => {
                // Either assignment or expression statement starting with LVal.
                if children.len() >= 3 && children[1].as_rule() == Rule::ASSIGN {
                    let lval = children[0].clone();
                    let exp = children.iter().find(|p| p.as_rule() == Rule::Exp).cloned();
                    if let Some(exp_pair) = exp {
                        self.check_assignment(lval, exp_pair);
                    }
                } else {
                    self.evaluate_lval(children[0].clone(), LValueUsage::RValue);
                }
            }
            Rule::Exp => {
                self.evaluate_exp(children[0].clone());
            }
            Rule::Block => self.visit_block(children[0].clone()),
            Rule::IF => self.visit_if_stmt(&children),
            Rule::WHILE => self.visit_while_stmt(&children),
            Rule::BREAK | Rule::CONTINUE => {}
            Rule::RETURN => {
                let expr = children.iter().find(|p| p.as_rule() == Rule::Exp).cloned();
                self.check_return(expr, line_of(&children[0]));
            }
            _ => {}
        }
    }

    fn visit_if_stmt(&mut self, children: &[Pair<Rule>]) {
        let mut then_stmt: Option<Pair<Rule>> = None;
        let mut else_stmt: Option<Pair<Rule>> = None;
        for child in children {
            match child.as_rule() {
                Rule::Cond => self.check_condition(child.clone()),
                Rule::Stmt => {
                    if then_stmt.is_none() {
                        then_stmt = Some(child.clone());
                    } else {
                        else_stmt = Some(child.clone());
                    }
                }
                _ => {}
            }
        }
        if let Some(stmt) = then_stmt {
            self.visit_stmt(stmt);
        }
        if let Some(stmt) = else_stmt {
            self.visit_stmt(stmt);
        }
    }

    fn visit_while_stmt(&mut self, children: &[Pair<Rule>]) {
        let mut body: Option<Pair<Rule>> = None;
        for child in children {
            match child.as_rule() {
                Rule::Cond => self.check_condition(child.clone()),
                Rule::Stmt => body = Some(child.clone()),
                _ => {}
            }
        }
        if let Some(stmt) = body {
            self.visit_stmt(stmt);
        }
    }

    fn check_assignment(&mut self, lval: Pair<Rule>, exp: Pair<Rule>) {
        let line = line_of(&lval);
        let left_ty = self.evaluate_lval(lval, LValueUsage::LValue);
        let right_ty = self.evaluate_exp(exp);

        if let (Some(lhs), Some(rhs)) = (left_ty, right_ty) {
            if !is_assignable(&lhs, &rhs) {
                self.push_error(line, ErrorKind::AssignmentMismatch);
            }
        }
    }

    fn check_initializer(&mut self, expected: &Type, init: Pair<Rule>, line: usize) {
        match init.as_rule() {
            Rule::InitVal | Rule::ConstInitVal => {
                // For non-array types the initializer must evaluate to int.
                if !matches!(expected, Type::Array(_)) {
                    if let Some(actual) = self.evaluate_initializer(init) {
                        if !is_assignable(expected, &actual) {
                            self.push_error(line, ErrorKind::AssignmentMismatch);
                        }
                    }
                }
            }
            _ => {}
        }
    }

    fn evaluate_initializer(&mut self, init: Pair<Rule>) -> Option<Type> {
        for child in init.into_inner() {
            match child.as_rule() {
                Rule::Exp | Rule::ConstExp => return self.evaluate_exp(child),
                Rule::InitVal | Rule::ConstInitVal => return self.evaluate_initializer(child),
                _ => {}
            }
        }
        Some(Type::Int)
    }

    fn check_condition(&mut self, cond: Pair<Rule>) {
        if let Some(ty) = self.evaluate_cond(cond.clone()) {
            if !matches!(ty, Type::Int) {
                self.push_error(line_of(&cond), ErrorKind::OperandMismatch);
            }
        }
    }

    fn check_return(&mut self, expr: Option<Pair<Rule>>, line: usize) {
        let Some(expected) = self.current_return.clone() else { return; };
        match expected {
            Type::Void => {
                if let Some(exp_pair) = expr {
                    let ty = self.evaluate_exp(exp_pair);
                    if ty.is_some() {
                        self.push_error(line, ErrorKind::ReturnMismatch);
                    }
                }
            }
            Type::Int => {
                if let Some(exp_pair) = expr {
                    let ty = self.evaluate_exp(exp_pair);
                    if !matches!(ty, Some(Type::Int)) {
                        self.push_error(line, ErrorKind::ReturnMismatch);
                    }
                } else {
                    self.push_error(line, ErrorKind::ReturnMismatch);
                }
            }
            _ => {}
        }
    }

    fn evaluate_cond(&mut self, pair: Pair<Rule>) -> Option<Type> {
        self.evaluate_lor_exp(pair.into_inner().next()? )
    }

    fn evaluate_exp(&mut self, pair: Pair<Rule>) -> Option<Type> {
        match pair.as_rule() {
            Rule::Exp | Rule::ConstExp => {
                let mut inner = pair.into_inner();
                let first = inner.next()?;
                self.evaluate_add_exp(first)
            }
            Rule::AddExp => self.evaluate_add_exp(pair),
            _ => None,
        }
    }

    fn evaluate_add_exp(&mut self, pair: Pair<Rule>) -> Option<Type> {
        let mut inner = pair.into_inner();
        let mut current = self.evaluate_mul_exp(inner.next()?);
        let mut pending_op: Option<Pair<Rule>> = None;
        while let Some(token) = inner.next() {
            match token.as_rule() {
                Rule::PLUS | Rule::MINUS => pending_op = Some(token),
                _ => {
                    let rhs = self.evaluate_mul_exp(token);
                    if let Some(op) = pending_op.take() {
                        current = self.check_int_binary(current, rhs, line_of(&op));
                    }
                }
            }
        }
        current
    }

    fn evaluate_mul_exp(&mut self, pair: Pair<Rule>) -> Option<Type> {
        let mut inner = pair.into_inner();
        let mut current = self.evaluate_unary_exp(inner.next()?);
        let mut pending_op: Option<Pair<Rule>> = None;
        while let Some(token) = inner.next() {
            match token.as_rule() {
                Rule::MUL | Rule::DIV | Rule::MOD => pending_op = Some(token),
                _ => {
                    let rhs = self.evaluate_unary_exp(token);
                    if let Some(op) = pending_op.take() {
                        current = self.check_int_binary(current, rhs, line_of(&op));
                    }
                }
            }
        }
        current
    }

    fn evaluate_rel_exp(&mut self, pair: Pair<Rule>) -> Option<Type> {
        let mut inner = pair.into_inner();
        let mut current = self.evaluate_add_exp(inner.next()?);
        let mut pending_op: Option<Pair<Rule>> = None;
        while let Some(token) = inner.next() {
            match token.as_rule() {
                Rule::LT | Rule::GT | Rule::LE | Rule::GE => pending_op = Some(token),
                _ => {
                    let rhs = self.evaluate_add_exp(token);
                    if let Some(op) = pending_op.take() {
                        current = self.check_int_binary(current, rhs, line_of(&op));
                    }
                }
            }
        }
        Some(Type::Int)
    }

    fn evaluate_eq_exp(&mut self, pair: Pair<Rule>) -> Option<Type> {
        let mut inner = pair.into_inner();
        let mut current = self.evaluate_rel_exp(inner.next()?);
        let mut pending_op: Option<Pair<Rule>> = None;
        while let Some(token) = inner.next() {
            match token.as_rule() {
                Rule::EQ | Rule::NEQ => pending_op = Some(token),
                _ => {
                    let rhs = self.evaluate_rel_exp(token);
                    if let Some(op) = pending_op.take() {
                        current = self.check_int_binary(current, rhs, line_of(&op));
                    }
                }
            }
        }
        Some(Type::Int)
    }

    fn evaluate_land_exp(&mut self, pair: Pair<Rule>) -> Option<Type> {
        let mut inner = pair.into_inner();
        let mut current = self.evaluate_eq_exp(inner.next()?);
        while let Some(token) = inner.next() {
            match token.as_rule() {
                Rule::AND => {
                    let rhs = self.evaluate_eq_exp(inner.next()?);
                    current = self.check_int_binary(current, rhs, line_of(&token));
                }
                _ => {}
            }
        }
        Some(Type::Int)
    }

    fn evaluate_lor_exp(&mut self, pair: Pair<Rule>) -> Option<Type> {
        let mut inner = pair.into_inner();
        let mut current = self.evaluate_land_exp(inner.next()?);
        while let Some(token) = inner.next() {
            match token.as_rule() {
                Rule::OR => {
                    let rhs = self.evaluate_land_exp(inner.next()?);
                    current = self.check_int_binary(current, rhs, line_of(&token));
                }
                _ => {}
            }
        }
        Some(Type::Int)
    }

    fn evaluate_unary_exp(&mut self, pair: Pair<Rule>) -> Option<Type> {
        let children: Vec<_> = pair.clone().into_inner().collect();
        if children.is_empty() {
            return None;
        }
        match children[0].as_rule() {
            Rule::IDENT => self.evaluate_function_call(&children),
            Rule::PrimaryExp => self.evaluate_primary_exp(children[0].clone()),
            Rule::UnaryOp => {
                let op_line = line_of(&children[0]);
                let ty = self.evaluate_unary_exp(children.get(1)?.clone());
                if !matches!(ty, Some(Type::Int)) {
                    self.push_error(op_line, ErrorKind::OperandMismatch);
                }
                Some(Type::Int)
            }
            _ => self.evaluate_primary_exp(children[0].clone()),
        }
    }

    fn evaluate_function_call(&mut self, children: &[Pair<Rule>]) -> Option<Type> {
        let ident = &children[0];
        let name = ident.as_str();
        let line = line_of(ident);
        let mut args_pair: Option<Pair<Rule>> = None;
        for child in &children[1..] {
            if child.as_rule() == Rule::FuncRParams {
                args_pair = Some(child.clone());
                break;
            }
        }
        let args = args_pair
            .map(|p| self.collect_arguments(p))
            .unwrap_or_else(Vec::new);

        let symbol = self.lookup(name).cloned();
        match symbol {
            Some(SymbolKind::Function(func_ty)) => {
                if !arguments_match(&func_ty.params_type, &args) {
                    self.push_error(line, ErrorKind::FunctionNotApplicable);
                }
                Some(*func_ty.ret_ty.clone())
            }
            Some(SymbolKind::Variable { .. }) => {
                self.push_error(line, ErrorKind::NotFunction { name: name.to_string() });
                Some(Type::Int)
            }
            None => {
                self.push_error(line, ErrorKind::UndefinedFunction { name: name.to_string() });
                Some(Type::Int)
            }
        }
    }

    fn collect_arguments(&mut self, pair: Pair<Rule>) -> Vec<Type> {
        let mut args = Vec::new();
        for child in pair.into_inner() {
            if child.as_rule() == Rule::Exp {
                if let Some(ty) = self.evaluate_exp(child) {
                    args.push(ty);
                }
            }
        }
        args
    }

    fn evaluate_primary_exp(&mut self, pair: Pair<Rule>) -> Option<Type> {
        let children: Vec<_> = pair.clone().into_inner().collect();
        if children.is_empty() {
            match pair.as_rule() {
                Rule::Number => Some(Type::Int),
                _ => None,
            }
        } else {
            match children[0].as_rule() {
                Rule::LVal => self.evaluate_lval(children[0].clone(), LValueUsage::RValue),
                Rule::Number => Some(Type::Int),
                Rule::Exp => self.evaluate_exp(children[0].clone()),
                _ => Some(Type::Int),
            }
        }
    }

    fn evaluate_lval(&mut self, pair: Pair<Rule>, usage: LValueUsage) -> Option<Type> {
        let mut inner = pair.clone().into_inner();
        let Some(ident) = inner.next() else { return None; };
        let name = ident.as_str().to_string();
        let line = line_of(&ident);

        let Some(symbol) = self.lookup(&name) else {
            self.push_error(line, ErrorKind::UndefinedVariable { name });
            return None;
        };

        let mut ty = match symbol {
            SymbolKind::Variable { ty, .. } => ty.clone(),
            SymbolKind::Function(func_ty) => {
                if usage == LValueUsage::LValue {
                    self.push_error(line, ErrorKind::LeftValueRequired);
                    return None;
                }
                Type::Function(func_ty.clone())
            }
        };

        let remaining_children: Vec<_> = inner.collect();
        let mut idx = 0;
        while idx < remaining_children.len() {
            match remaining_children[idx].as_rule() {
                Rule::L_BRACKT => {
                    if let Some(next) = remaining_children.get(idx + 1) {
                        if next.as_rule() == Rule::Exp {
                            self.evaluate_exp(next.clone());
                        }
                    }
                    if let Type::Array(array_ty) = ty {
                        ty = *array_ty.contained;
                    } else {
                        self.push_error(line, ErrorKind::NotArray { name: ident.as_str().to_string() });
                        return None;
                    }
                }
                Rule::Exp => {
                    self.evaluate_exp(remaining_children[idx].clone());
                }
                _ => {}
            }
            idx += 1;
        }

        Some(ty)
    }

    fn check_int_binary(&mut self, lhs: Option<Type>, rhs: Option<Type>, line: usize) -> Option<Type> {
        let left_ok = matches!(lhs, Some(Type::Int));
        let right_ok = matches!(rhs, Some(Type::Int));
        if !left_ok || !right_ok {
            self.push_error(line, ErrorKind::OperandMismatch);
        }
        Some(Type::Int)
    }

    fn declare(&mut self, name: &str, line: usize, symbol: SymbolKind) -> bool {
        let scope = self.scopes.last_mut().expect("scope stack not empty");
        if scope.symbols.contains_key(name) {
            match &symbol {
                SymbolKind::Function(_) => self.push_error(line, ErrorKind::RedefinedFunction { name: name.to_string() }),
                SymbolKind::Variable { .. } => self.push_error(line, ErrorKind::RedefinedVariable { name: name.to_string() }),
            }
            return false;
        }
        scope.symbols.insert(name.to_string(), symbol);
        true
    }

    fn lookup(&self, name: &str) -> Option<&SymbolKind> {
        for scope in self.scopes.iter().rev() {
            if let Some(symbol) = scope.symbols.get(name) {
                return Some(symbol);
            }
        }
        None
    }

    fn push_error(&mut self, line: usize, kind: ErrorKind) {
        self.errors.push(kind.into_error(line));
    }

    fn enter_scope(&mut self) {
        self.scopes.push(Scope::default());
    }

    fn exit_scope(&mut self) {
        self.scopes.pop();
    }
}

#[derive(Clone, Copy, PartialEq, Eq)]
enum LValueUsage {
    LValue,
    RValue,
}

#[derive(Debug, Clone)]
struct Parameter {
    name: String,
    line: usize,
    ty: Type,
}

enum ErrorKind {
    UndefinedVariable { name: String },
    UndefinedFunction { name: String },
    RedefinedVariable { name: String },
    RedefinedFunction { name: String },
    AssignmentMismatch,
    OperandMismatch,
    ReturnMismatch,
    FunctionNotApplicable,
    NotArray { name: String },
    NotFunction { name: String },
    LeftValueRequired,
    Invalid(&'static str),
}

impl ErrorKind {
    fn into_error(self, line: usize) -> SemanticError {
        let (code, message) = match self {
            ErrorKind::UndefinedVariable { name } => (1, format!("Undefined variable: {name}.")),
            ErrorKind::UndefinedFunction { name } => (2, format!("Undefined function: {name}.")),
            ErrorKind::RedefinedVariable { name } => (3, format!("Redefined variable: {name}.")),
            ErrorKind::RedefinedFunction { name } => (4, format!("Redefined function: {name}.")),
            ErrorKind::AssignmentMismatch => (5, "type.Type mismatched for assignment.".to_string()),
            ErrorKind::OperandMismatch => (6, "type.Type mismatched for operands.".to_string()),
            ErrorKind::ReturnMismatch => (7, "type.Type mismatched for return.".to_string()),
            ErrorKind::FunctionNotApplicable => (8, "Function is not applicable for arguments.".to_string()),
            ErrorKind::NotArray { name } => (9, format!("Not an array: {name}.")),
            ErrorKind::NotFunction { name } => (10, format!("Not a function: {name}.")),
            ErrorKind::LeftValueRequired => (11, "The left-hand side of an assignment must be a variable.".to_string()),
            ErrorKind::Invalid(message) => (0, message.to_string()),
        };
        SemanticError::new(code, line, message)
    }
}

fn line_of(pair: &Pair<Rule>) -> usize {
    pair.as_span().start_pos().line_col().0
}

fn make_array_type(base: Type, dims: usize) -> Type {
    if dims == 0 {
        return base;
    }
    let mut ty = base;
    for _ in 0..dims {
        ty = Type::Array(ArrayType { contained: Box::new(ty), num_elements: None });
    }
    ty
}

fn count_dimensions(mut iter: Pairs<Rule>) -> usize {
    let mut count = 0;
    while let Some(token) = iter.next() {
        if token.as_rule() == Rule::L_BRACKT {
            count += 1;
        }
    }
    count
}

fn is_assignable(lhs: &Type, rhs: &Type) -> bool {
    match lhs {
        Type::Int => matches!(rhs, Type::Int),
        Type::Array(_) => matches!(rhs, Type::Array(_)),
        Type::Function(_) => matches!(rhs, Type::Function(_)),
        Type::Void => matches!(rhs, Type::Void),
    }
}

fn arguments_match(expected: &[Type], provided: &[Type]) -> bool {
    if expected.len() != provided.len() {
        return false;
    }
    expected.iter().zip(provided).all(|(exp, got)| match (exp, got) {
        (Type::Int, Type::Int) => true,
        (Type::Array(_), Type::Array(_)) => true,
        _ => false,
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    fn single_error(source: &str) -> SemanticError {
        let errors = analyze(source).unwrap_err();
        assert_eq!(errors.len(), 1);
        errors.into_iter().next().unwrap()
    }

    #[test]
    fn duplicate_variable_in_block_reports_error() {
        let src = r#"int main() {
    int x;
    int x;
    return 0;
}"#;

        let err = single_error(src);
        assert_eq!(err.code, 3);
        assert!(err.message.contains("x"));
    }

    #[test]
    fn duplicate_function_reports_error() {
        let src = r#"int foo() { return 0; }
int foo() { return 1; }
int main() { return foo(); }
"#;

        let err = single_error(src);
        assert_eq!(err.code, 4);
        assert!(err.message.contains("foo"));
    }

    #[test]
    fn parameter_redefinition_detected() {
        let src = r#"int foo(int x, int x) { return x; }
int main() { return foo(1, 2); }
"#;

        let errors = analyze(src).unwrap_err();
        assert!(errors.iter().any(|e| e.code == 3));
    }

    #[test]
    fn simple_program_without_redefinition_passes() {
        let src = r#"int add(int a, int b) {
    int result = a + b;
    return result;
}

int main() {
    return add(1, 2);
}
"#;

        assert!(analyze(src).is_ok());
    }

    #[test]
    fn doc_example_with_errors_matches_expected_output() {
        let input = std::fs::read_to_string("tests/lab3-tests/sample1.in").expect("test fixture");
        let expected = std::fs::read_to_string("tests/lab3-tests/sample1.expected").expect("expected output");
        let errors = analyze(&input).expect_err("expected semantic errors");
        let actual = errors
            .iter()
            .map(|err| format!("Error type {} at Line {}: {}", err.code, err.line, err.message))
            .collect::<Vec<_>>()
            .join("\n");
        assert_eq!(actual.trim_end(), expected.trim_end());
    }

    #[test]
    fn doc_example_without_errors_matches_expected_output() {
        let input = std::fs::read_to_string("tests/lab3-tests/sample2.in").expect("test fixture");
        let expected = std::fs::read_to_string("tests/lab3-tests/sample2.expected").expect("expected output");
        match analyze(&input) {
            Ok(()) => assert_eq!(expected.trim_end(), "No semantic errors in the program!"),
            Err(errors) => {
                let actual = errors
                    .iter()
                    .map(|err| format!("Error type {} at Line {}: {}", err.code, err.line, err.message))
                    .collect::<Vec<_>>()
                    .join("\n");
                panic!("expected success, got errors:\n{}", actual);
            }
        }
    }
}
